﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Linq;
using System.Text;

using alib.Enumerable;
using alib.String;

using agree.configuration;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{Name}")]
	public sealed class StartSymbol : StaticExpandEntry
	{
		public static readonly StartSymbol Null = new StartSymbol(default(sync_obj_ctor_arg));

		private StartSymbol(sync_obj_ctor_arg _) : base(_) { }

		public StartSymbol(Grammar g, Type t, ConsDefs consdef)
			: base(g, t, consdef)
		{
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Required property init:
	///		TfsUnifier
	///		EntryMgr
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DisplayName("Grammar node labeler")]
	public sealed class NodeLabeler : has_grammar_base, ISupportInitializeNotification
	{
		public NodeLabeler(IRtParent parent)
			: base(parent)
		{
			this.label_templates = alib.Collections.Collection<NodeLabelTemplate>.None;
			this.local_label_templates = alib.Collections.Collection<NodeLabelTemplate>.None;
			this.meta_templates = alib.Collections.Collection<NodeMetaTemplate>.None;
		}

		NodeLabelsConfig nlc;

		public Type tt_label;
		public Type tt_meta;

		public String s_node_label;
		public String s_node_meta;

		NodeLabelTemplate[] label_templates;
		NodeLabelTemplate[] local_label_templates;
		NodeMetaTemplate[] meta_templates;

		public void EndInit()
		{
			this.nlc = AgreeConfig.NodeLabels;

			s_node_label = nlc.LabelTemplateType;
			if (s_node_label == null || !td.TryGetType(s_node_label, out tt_label))
			{
				tt_label = td.ΔTop;
				//throw new TdlException("The node label template type '{0}' was not defined in any TDL file.", tcfg.LabelTemplateType);
			}

			s_node_meta = nlc.MetaTemplateType;
			if (s_node_meta == null || !td.TryGetType(s_node_meta, out tt_meta))
			{
				tt_meta = td.ΔTop;
				//throw new TdlException("The node meta template type '{0}' was not defined in any TDL file.", config._NodeLabelConfiguration.MetaTemplateType);
			}

			foreach (var nt in em._node_labels)
			{
				var exp = nt.Expanded;
			}

			label_templates = em._node_labels.OfType<NodeLabelTemplate>().ToArray();
			local_label_templates = label_templates.Where(nlt => nlt.effective_local.IsValid).ToArray();

			meta_templates = em._node_labels.OfType<NodeMetaTemplate>().ToArray();
		}

		public String FindLabel(Tfs tfs)
		{
			String label = nlc.SimpleTreeDisplay ? find_label_simple(tfs) : find_label(tfs);

			if (label != null && nlc.UppercaseLabel)
				label = label.ToUpper();

			return label;
		}

		String find_label_simple(Tfs tfs)
		{
			foreach (NodeLabelTemplate nlt in label_templates)
			{
				if (fsu.UnifyCheck(nlt.effective, tfs))
					return nlt.Label;
			}
			return null;
		}

		String find_label(Tfs tfs)
		{
			TfsSlot _tmp;
			if (!nlc.LabelFsPath.IsEmpty && nlc.LabelFsPath.Count > 0)
			{
				if (!nlc.LabelFsPath.GetSlot(tfs, out _tmp))
					return null;
			}
			else
				_tmp = new TfsSlot(tfs, 0);

			foreach (NodeLabelTemplate nlt in label_templates)
			{
				if (fsu.UnifyCheck(nlt.effective, _tmp))
				{
					String s_label = nlt.Label;
					TfsSlot ts_meta;
					if (meta_templates.Length > 0 && nlc.RecursivePath.GetSlot(tfs, out ts_meta))
					{
						foreach (NodeMetaTemplate nmt in meta_templates)
						{
							if (fsu.UnifyCheck(nmt.effective, ts_meta))
							{
								foreach (NodeLabelTemplate nlt_sub in label_templates)
								{
									if (fsu.UnifyCheck(ts_meta, nlt_sub.effective_local))
										return s_label + nmt.Prefix + nlt_sub.Label + nmt.Suffix;
								}
							}
						}
					}
					return s_label;
				}
			}
			return null;
		}

#if false
		public ParseTree GetParseTree(ParseChart pc, IDerivation pe)
		{
			return new ParseTree(this, pc, pe);
		}

		public class ParseTree
		{
			static readonly ParseTree[] rg_pt_empty = new ParseTree[0];

			public ParseTree(GrammarNodeLabeler gnl, ParseChart pc, IDerivation pce)
			{
				this.pc = pc;
				this.drv = pce;
				this.label = gnl.FindLabel(pce.Tfs) ?? pce.Tfs.Name;
				this.children = pce.Daughters.Select(d => new ParseTree(gnl, pc, d)).ToArray();
			}

			readonly ParseChart pc;
			readonly IDerivation drv;
			readonly ParseTree[] children;
			readonly String label;

			public ParseTree[] Children { get { return children; } }

			public String Label { get { return label; } }

			public String SourceText
			{
				get
				{
					var tok = drv as ITokenSpan;
					if (tok != null)
						return tok.Text;
					return "???";// pc.ctrl.ts_input.Source.MinimalSpanText(drv);
				}
			}

			public override String ToString()
			{
				return children.Length == 0 ?
							label :
							String.Format("{0} ({1})", label, children.StringJoin(" "));
			}

			public String MonospaceFormat()
			{
				var ca = children.Select(c => c.MonospaceFormat().Split(alib.Character.Charset.cr_lf, StringSplitOptions.RemoveEmptyEntries)).ToArray();
				String s = String.Format("{0}", label);
				if (ca.Length > 0)
				{
					int width = ca.Max(c => c.Max(s_lab => s_lab.Length)) + 2;
					int j_max = ca.Max(c => c.Length);

					StringBuilder sb = new StringBuilder();
					sb.AppendLine(s.PadCenter(width * ca.Length, ' '));

					for (int j = 0; j < j_max; j++)
					{
						for (int q = 0; q < ca.Length; q++)
						{
							String s0;
							if (j < ca[q].Length)
								s0 = ca[q][j];
							else
								s0 = "";
							sb.Append(s0.PadCenter(width, ' '));
						}
						sb.AppendLine();
					}
					s = sb.ToString();
				}
				else
					s += Environment.NewLine;
				return s;
			}
		};
#endif
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class NodeLabel : StaticExpandEntry
	{
		protected NodeLabel(Grammar g, Type t, ConsDefs consdef)
			: base(g, t, consdef)
		{
		}

		protected NodeLabelsConfig nlc { get { return config.NodeLabels; } }

		public TfsSlot effective;
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{Name}")]
	public sealed class NodeLabelTemplate : NodeLabel
	{
		public NodeLabelTemplate(Grammar g, Type t, ConsDefs consdef)
			: base(g, t, consdef)
		{
		}

		public String Label;
		public TfsSlot effective_local;

		protected override Tfs EnsureExpanded()
		{
			var _exp = base.EnsureExpanded();

			var _def = _exp;// this.Definition;// this.Expanded;

			if (nlc.LabelPath.IsEmpty || em.AgreeConfig.NodeLabels.SimpleTreeDisplay)	// todo: make a derived class for simple labels
			{
				Label = this.Name;
				effective = new TfsSlot(_def, 0);
				return _exp;
			}

			var ts = _def[new FsPath(ftm.im, nlc.LabelPath)];
			if (!ts.IsValid)
				throw new GrammarLoadException("Node label template '{0}' has no label at '{1}'",
						Name,
						nlc.LabelPath.StringJoin("."));
			Label = ts.StringValue;

			/// Sharing the Mark of 'Expanded' but with a more derived type (computed here) causes 
			/// the 'label' feature to be ignored when unifying for node labels
			Type t_new = im.GetMaximalTypeForFeatures(_def.RootFeatures().Exclude(ts.i_feat));
			if (_def is ArrayTfs)
				((ArrayTfs)_def)._top_edge = new Edge(t_new.EdgeFlag, _def._top_edge.Mark);
			else
				throw new Exception();

			/// todo -- use restrictor instead
			//exp.GetSection(exp.tm.CreateEdge(t_new, exp.TopEdge.Mark, false), out effective);
			effective = new TfsSlot(_def, 0);

			if (!nlc.LocalPath.GetSlot(_def, out effective_local))
				effective_local = new TfsSlot(_def, 0);

			//effective_local = default(TfsSlot);
			return _exp;
		}

		public override String ToString()
		{
			return String.Format("{0} - {1}", Label, Name);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{Name}")]
	public sealed class NodeMetaTemplate : NodeLabel
	{
		public NodeMetaTemplate(Grammar g, Type t, ConsDefs consdef)
			: base(g, t, consdef)
		{
		}

		public String Prefix;
		public String Suffix;

		protected override Tfs EnsureExpanded()
		{
			var _exp = base.EnsureExpanded();

			var _def = _exp;// this.Definition;//.Expanded;

			var ts = _def[new FsPath(im, nlc.PrefixPath)];
			if (!ts.IsValid)
				throw new GrammarLoadException("Node meta template '{0}' has no prefix at '{1}'",
							Name,
							nlc.PrefixPath.StringJoin("."));

			Prefix = ts.StringValue;

			ts = _def[new FsPath(im, nlc.SuffixPath)];
			if (!ts.IsValid)
				throw new GrammarLoadException("Node meta template '{0}' has no suffix at '{1}'",
							Name,
							nlc.SuffixPath.StringJoin("."));

			Suffix = ts.StringValue;

			/// Sharing the Mark of 'Expanded' but with a more derived type (computed here) causes 
			/// the 'prefix' and 'suffix' features to be ignored when unifying
			/// for now, assuming that the prefix and suffix features are introduced by meta
			Type t_new = im.GetMaximalTypeForFeatures(_def.RootFeatures().Where(fix => ftm[fix].introduced_by != nl.tt_meta));

			if (!nlc.RecursivePath.GetSlot(_def, out effective))
				throw new Exception();

			return _exp;
		}
	};
}
